import random
import time

import numpy as np
from ortools.sat.python import cp_model


def load_fun_3(job_data, job_type, solution_bool_sub_job, car_type):
    '''
    装车模型
    :param job_data: EMS任务的具体信息，即货物数量，该项包含n个任务和各个任务对应的子任务的货物数量,同时任务的优先级默认从高到低依次排列
    :param job_type: EMS货物形态 0可代表栈板，1代表件箱，2代表gtp
    :param solution_bool_sub_job: 该项对应某一个子任务是否被处理，若为1则被处理，若为0则没被处理
    :param car_type: 每一个厂家货物对应的车辆类型
    :return: car:每辆车装载的货物量
    job_re:记录每辆车装载的货物编号
    re_job:剩余还没处理的货物
    '''
    car = []
    sub_car = []
    job_re = []
    sub_job_re = []
    re_job = []
    f_p = [0.1, 0.15]
    max_c = [300, 360]
    tr = [15, 18]
    r_j = 0
    c = 0
    ch = 0

    for i in range(len(job_data)):
        c = 0
        sub_car = []
        sub_job_re = []
        for j in range(len(job_data[i])):
            if solution_bool_sub_job[i][j] == 1:
                if job_type[i][j] != 0:
                    ch = job_data[i][j]
                else:
                    ch = job_data[i][j] * 20
                m_c = max_c[car_type[i]]
                while True:
                    if c + ch <= m_c:
                        c += ch
                        sub_car.append(ch)
                        sub_job_re.append([i, j])
                        if c == m_c:
                            sub_car = []
                            sub_job_re = []
                            c = 0
                        break
                    elif c < m_c and c + ch > m_c:
                        if j + 1 == len(job_data[i]):
                            if c + ch <= m_c * (1 + f_p[car_type[i]]):
                                sub_car.append(c + ch)
                                sub_job_re.append([i, j])
                                car.append(sub_car)
                                job_re.append(sub_job_re)
                                break
                            else:
                                if job_type[i][j] != 0:
                                    sub_car.append(m_c - c)
                                    sub_job_re.append([i, j])
                                    car.append(sub_car)
                                    job_re.append(sub_job_re)
                                    sub_car = []
                                    sub_job_re = []
                                    ch = ch - (m_c - c)
                                    c = 0
                                else:
                                    rr = (m_c - c) // 20
                                    sub_car.append(rr * 20)
                                    sub_job_re.append([i, j])
                                    car.append(sub_car)
                                    job_re.append(sub_job_re)
                                    sub_car = []
                                    sub_job_re = []
                                    ch = ch - rr * 20
                                    c = 0

                        else:
                            if job_type[i][j] == 0:
                                rr = (m_c - c) // 20
                                sub_car.append(rr * 20)
                                sub_job_re.append([i, j])
                                car.append(sub_car)
                                job_re.append(sub_job_re)
                                sub_car = []
                                sub_job_re = []
                                ch = ch - rr * 20
                                c = 0
                            else:
                                sub_car.append(m_c - c)
                                sub_job_re.append([i, j])
                                car.append(sub_car)
                                job_re.append(sub_job_re)
                                sub_car = []
                                sub_job_re = []
                                ch = ch - (m_c - c)
                                c = 0

            else:
                re_job.append([i, j])

    return car, job_re, re_job


def generate_data(job_num, op_num_low, op_num_high):
    """
    该函数用于生成数据，输入输出参数解释如下
    :param job_num: 任务的数量
    :param op_num_low: 生成子任务数量的下限
    :param op_num_high: 生成子任务数量的上限
    :return: data_end:任务的具体信息，即货物数量，该项包含n个任务和各个任务对应的子任务的货物数量
    job_type:货物形态
    job_p:任务的优先级
    se:是否为自制
    d_list:任务的截止时间
    """
    np.random.seed(12)
    random.seed(12)
    due_data = [18 * 60, 21 * 60, 24 * 60]
    start_se = [8 * 60, 9 * 60, 10 * 60, 11 * 60, 13 * 60, 15 * 60]
    car_type = []
    # due_data_se = []
    d = []
    data_end = []
    data_end_se = []
    data_start_se = []
    # due_data = []
    job_type = []
    job_se_type = []
    job_p = []
    se = []
    d_list = []
    d_list_se = []
    n = np.random.randint(job_num // 2 + 1, job_num)

    for i in range(job_num):
        job_list = []
        op_type = []
        # d_list.append(random.sample(due_data, 1)[0])
        op_num = np.random.randint(op_num_low, op_num_high)
        car_type.append(np.random.randint(0, 2))
        if i <= n:
            for j in range(op_num):
                # num = np.random.randint(num_low, num_high)
                ty = np.random.randint(0, 3)
                if ty == 0:
                    num = np.random.randint(40, 250)
                elif ty == 1:
                    num = np.random.randint(200, 800)
                elif ty == 2:
                    num = np.random.randint(200, 800)
                # s = np.random.randint(0, 2)
                op_type.append(ty)
                job_list.append(num)  # d_list.append(random.sample(due_data, 1))
            data_end.append(job_list)
            d_list.append(random.sample(due_data, 1)[0])
            # d.append(d_list)
            job_type.append(op_type)  # job_p.append(i)
        else:
            for j in range(op_num):
                # num = np.random.randint(num_low, num_high)
                ty = np.random.randint(0, 3)
                if ty == 0:
                    num = np.random.randint(40, 250)
                elif ty == 1:
                    num = np.random.randint(200, 800)
                elif ty == 2:
                    num = np.random.randint(200, 800)
                # s = np.random.randint(0, 2)
                op_type.append(0)
                job_list.append(num)  # d_list.append(random.sample(due_data, 1))
            data_end_se.append(job_list)
            job_se_type.append(op_type)
            d_list_se.append(random.sample(due_data, 1)[0])
            data_start_se.append(
                random.sample(start_se, 1)[0])  # d.append(d_list)  # job_type.append(op_type)  # job_p.append(i)

    return data_end, data_end_se, job_type, d_list, d_list_se, data_start_se, job_se_type, car_type


def se_solve(job_data_se, d_list_se, data_start_se, t):
    V_j = [2, 17, 3]
    t_t_list = [i * 2 * 60 for i in range(1, 13)]
    total_C_list = []
    total_C_se_list = []
    bool_job_list = []
    bool_job_se_list = []
    bool_sub_job_list = []
    bool_sub_job_se_list = []
    job_p_list = []
    job_p_se_list = []
    x_intervals = []
    y_intervals = []
    solution_x_total = []
    solution_bool_sub_job = []
    solution_bool_C = []
    de = []
    total_b = 0
    total_bb = 0
    model = cp_model.CpModel()
    max_time = cp_model.INT32_MAX

    for i in range(len(job_data_se)):
        j_b_se = model.NewBoolVar('%d' % i)
        total_bb += j_b_se
        bool_job_se_list.append(j_b_se)
        job_p_se_sub = []
        C_se_list = []
        bool_sub_job_se = []
        for j in range(len(job_data_se[i])):
            j_sub_b_se = model.NewBoolVar('%d_%d' % (i, j))
            bool_sub_job_se.append(j_sub_b_se)
            total_b += j_sub_b_se
            sub_c_se_list = []
            sub_p_se_list = []
            j_sub_p_se_s = model.NewIntVar(data_start_se[i], 1441, 'p_s_%d_%d' % (i, j))
            j_sub_p_se_e = model.NewIntVar(data_start_se[i], 1441, 'p_e_%d_%d' % (i, j))
            j_sub_p_e_se_1 = model.NewIntVar(data_start_se[i], 1441, 'p_e1_%d_%d' % (i, j))
            due_data_se_d = model.NewBoolVar('due_data_d')
            c_se_d = model.NewBoolVar('c_d')
            model.Add(j_sub_p_se_e <= d_list_se[i]).OnlyEnforceIf(due_data_se_d)
            model.Add(j_sub_p_se_e > d_list_se[i]).OnlyEnforceIf(due_data_se_d.Not())
            sub_p_se_list.append(j_sub_p_se_s)
            sub_p_se_list.append(j_sub_p_se_e)
            sub_p_se_list.append(j_sub_p_e_se_1)
            job_p_se_sub.append(sub_p_se_list)
            b = model.NewBoolVar('%d_%d_%d' % (i, j, 8))
            sub_c_se_list.append(b)
            # if se[i]:
            #     for m in range(7):
            #         b = model.NewBoolVar('%d_%d_%d' % (i, j, m))
            #         sub_c_se_list.append(b)
            # else:
            #     for m in range(6):
            #         b = model.NewBoolVar('%d_%d_%d' % (i, j, m))
            #         sub_c_list.append(b)

            model.Add(sum(sub_c_se_list) > 0).OnlyEnforceIf(c_se_d)
            model.Add(sum(sub_c_se_list) == 0).OnlyEnforceIf(c_se_d.Not())
            model.Add(due_data_se_d == 1).OnlyEnforceIf(c_se_d)
            model.Add(c_se_d + due_data_se_d == 2).OnlyEnforceIf(j_sub_b_se)
            model.Add(c_se_d + due_data_se_d < 2).OnlyEnforceIf(j_sub_b_se.Not())
            C_se_list.append(sub_c_se_list)
        model.Add(sum(bool_sub_job_se) == len(bool_sub_job_se)).OnlyEnforceIf(j_b_se)
        model.Add(sum(bool_sub_job_se) < len(bool_sub_job_se)).OnlyEnforceIf(j_b_se.Not())
        total_C_se_list.append(C_se_list)
        bool_sub_job_se_list.append(bool_sub_job_se)
        job_p_se_list.append(job_p_se_sub)

    for x in range(len(t_t_list)):
        x_s = model.NewConstant(int(t_t_list[x] - 48))
        ii = model.NewConstant(48)
        x_e = model.NewConstant(t_t_list[x])
        y_s = model.NewConstant(0)
        iii = model.NewConstant(8)
        y_e = model.NewConstant(8)
        x_ii = model.NewOptionalIntervalVar(x_s, ii, x_e, 1, 'x_ii%d' % x)
        y_ii = model.NewOptionalIntervalVar(y_s, iii, y_e, 1, 'y_ii%d' % x)
        x_intervals.append(x_ii)
        y_intervals.append(y_ii)

    for i in range(len(job_data_se)):
        total_interval_se = 0
        x_t_intervals_se = []
        for j in range(len(job_data_se[i])):
            for k in range(len(total_C_se_list[i][j])):
                total_interval_se += total_C_se_list[i][j][k] * V_j[2]
            t_ii = model.NewIntVar(1, 999999, 't_ii_%d' % j)
            model.Add(t_ii == total_interval_se + 1)
            I_se = model.NewIntVar(0, 999999, 'In_%d' % j)
            model.AddDivisionEquality(I_se, job_data_se[i][j], t_ii)
            j_sub_p_I_se_intervals = model.NewOptionalIntervalVar(job_p_se_list[i][j][0], I_se + 30,
                                                                  job_p_se_list[i][j][2], bool_sub_job_se_list[i][j],
                                                                  '%d_%d_I' % (i, j))
            x_t_intervals_se.append(j_sub_p_I_se_intervals)
            # de.append(int(3))
            y_end = model.NewIntVar(0, 10, 'y_end_%d%d%d' % (i, j, 8))
            j_sub_p_se_intervals = model.NewOptionalIntervalVar(job_p_se_list[i][j][0], I_se + 1,
                                                                job_p_se_list[i][j][1], total_C_se_list[i][j][0],
                                                                'p_%d_%d' % (i, j))
            j_sub_c_se_intervals = model.NewOptionalIntervalVar(7, 1, y_end, total_C_se_list[i][j][0],
                                                                'c_%d_%d' % (i, j))
            x_intervals.append(j_sub_p_se_intervals)
            y_intervals.append(j_sub_c_se_intervals)

        model.AddNoOverlap(x_t_intervals_se)
    model.AddNoOverlap2D(x_intervals, y_intervals)

    model.Maximize(total_b)
    solver = cp_model.CpSolver()
    solver.parameters.num_search_workers = 8
    solver.parameters.max_time_in_seconds = t
    # solver.parameters.log_search_progress = True
    status = solver.Solve(model)

    if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
        print("status: ", status)

        for i in range(len(job_data_se)):
            solu_x_sub_se = []
            for j in range(len(job_data_se[i])):
                solution_x_se = []
                solution_x_se.append(solver.Value(job_p_se_list[i][j][0]))
                solution_x_se.append(solver.Value(job_p_se_list[i][j][1]))
                solu_x_sub_se.append(solution_x_se)
            solution_x_total.append(solu_x_sub_se)

        for i in range(len(job_data_se)):
            solu_bool_sub_se = []
            for j in range(len(job_data_se[i])):
                solu_bool_sub_se.append(solver.Value(bool_sub_job_se_list[i][j]))
            solution_bool_sub_job.append(solu_bool_sub_se)

        for i in range(len(job_data_se)):
            solu_C_se_sub = []
            for j in range(len(job_data_se[i])):
                solu_C_sub_sub_se = []
                for k in range(len(total_C_se_list[i][j])):
                    solu_C_sub_sub_se.append(solver.Value(total_C_se_list[i][j][k]))
                solu_C_se_sub.append(solu_C_sub_sub_se)
            solution_bool_C.append(solu_C_se_sub)

        print("youjie")
    else:
        print('No solution found.')

    return solution_x_total, solution_bool_sub_job, solution_bool_C


def solve(job_data, job_data_se, d_list, d_list_se, data_start_se, job_type, sxt, sbsj, sbc, t):
    """
    该函数用于求解，输入输出参数解释如下
    :param job_data:任务的具体信息，即货物数量，该项包含n个任务和各个任务对应的子任务的货物数量,同时任务的优先级默认从高到低依次排列
    :param job_type:货物形态
    :param se:是否为自制
    :param d:任务的截止时间
    :param t:求解时间，若最后无解，则需调大时间
    :return: solution_x_total 所有子任务的开始处理时间和处理结束时间，单位为分钟
    solution_bool_sub_job 该项对应某一个子任务是否被处理，若为1则被处理，若为0则没被处理
    solution_bool_C 该项对应子任务的通道使用情况，其中为1则该通道被使用，为0则没被使用
    """

    V_j = [2, 17, 3]
    t_t_list = [i * 2 * 60 for i in range(5, 13)]
    total_C_list = []
    total_C_se_list = []
    bool_job_list = []
    bool_job_se_list = []
    bool_sub_job_list = []
    bool_sub_job_se_list = []
    job_p_list = []
    job_p_se_list = []
    x_intervals = []
    y_intervals = []
    solution_x_total = []
    solution_bool_sub_job = []
    solution_bool_C = []
    de = []
    rs_c = []
    total_b = 0
    total_bb = 0

    model = cp_model.CpModel()
    max_time = cp_model.INT32_MAX
    print("job_data: ", job_data)
    # print("d: ", d)

    for i in range(len(job_data)):
        j_b = model.NewBoolVar('%d' % i)
        total_bb += j_b
        bool_job_list.append(j_b)
        job_p_sub = []
        C_list = []
        bool_sub_job = []
        for j in range(len(job_data[i])):
            j_sub_b = model.NewBoolVar('%d_%d' % (i, j))
            bool_sub_job.append(j_sub_b)
            total_b += j_sub_b
            sub_c_list = []
            sub_p_list = []
            j_sub_p_s = model.NewIntVar(480, 1441, 'p_s_%d_%d' % (i, j))
            j_sub_p_e = model.NewIntVar(480, 1441, 'p_e_%d_%d' % (i, j))
            j_sub_p_e_1 = model.NewIntVar(480, 1441, 'p_e1_%d_%d' % (i, j))
            due_data_d = model.NewBoolVar('due_data_d')
            c_d = model.NewBoolVar('c_d')
            model.Add(j_sub_p_e <= d_list[i]).OnlyEnforceIf(due_data_d)
            model.Add(j_sub_p_e > d_list[i]).OnlyEnforceIf(due_data_d.Not())
            sub_p_list.append(j_sub_p_s)
            sub_p_list.append(j_sub_p_e)
            sub_p_list.append(j_sub_p_e_1)
            job_p_sub.append(sub_p_list)

            if job_type[i][j] == 0:
                b = model.NewBoolVar('%d_%d_%d' % (i, j, 7))
                sub_c_list.append(b)
            else:
                for m in range(6):
                    b = model.NewBoolVar('%d_%d_%d' % (i, j, m))
                    sub_c_list.append(b)

            model.Add(sum(sub_c_list) > 0).OnlyEnforceIf(c_d)
            model.Add(sum(sub_c_list) == 0).OnlyEnforceIf(c_d.Not())
            model.Add(due_data_d == 1).OnlyEnforceIf(c_d)
            model.Add(c_d + due_data_d == 2).OnlyEnforceIf(j_sub_b)
            model.Add(c_d + due_data_d < 2).OnlyEnforceIf(j_sub_b.Not())
            C_list.append(sub_c_list)
        model.Add(sum(bool_sub_job) == len(bool_sub_job)).OnlyEnforceIf(j_b)
        model.Add(sum(bool_sub_job) < len(bool_sub_job)).OnlyEnforceIf(j_b.Not())
        total_C_list.append(C_list)
        bool_sub_job_list.append(bool_sub_job)
        job_p_list.append(job_p_sub)

    for x in range(len(t_t_list)):
        x_s = model.NewConstant(int(t_t_list[x] - 48))
        ii = model.NewConstant(48)
        x_e = model.NewConstant(t_t_list[x])
        y_s = model.NewConstant(0)
        iii = model.NewConstant(7)
        y_e = model.NewConstant(7)
        x_ii = model.NewOptionalIntervalVar(x_s, ii, x_e, 1, 'x_ii%d' % x)
        y_ii = model.NewOptionalIntervalVar(y_s, iii, y_e, 1, 'y_ii%d' % x)
        x_intervals.append(x_ii)
        y_intervals.append(y_ii)

    for g in range(len(sxt)):
        for i in range(len(sxt[g])):
            x_s = model.NewConstant(sxt[g][i][0])
            ii = model.NewConstant(sxt[g][i][1] - sxt[g][i][0])
            x_e = model.NewConstant(sxt[g][i][1])
            y_s = model.NewConstant(7)
            iii = model.NewConstant(1)
            y_e = model.NewConstant(8)
            # print("sbsj[g][i]: ", sbsj[g][i])
            if sbsj[g][i] == 1:
                x_ii = model.NewOptionalIntervalVar(x_s, ii, x_e, sbsj[g][i], 'x_ii%d' % i)
                y_ii = model.NewOptionalIntervalVar(y_s, iii, y_e, sbsj[g][i], 'y_ii%d' % i)
                x_intervals.append(x_ii)
                y_intervals.append(y_ii)
                de.append(int(3))
                rs_c.append(x_ii)

    for i in range(len(job_data)):
        total_interval = 0
        x_t_intervals = []
        for j in range(len(job_data[i])):
            for k in range(len(total_C_list[i][j])):
                total_interval += total_C_list[i][j][k] * V_j[job_type[i][j]]
            t_i = model.NewIntVar(1, 999999, 't_i_%d' % j)
            model.Add(t_i == total_interval + 1)
            I = model.NewIntVar(0, 999999, 'In_%d' % j)
            model.AddDivisionEquality(I, job_data[i][j], t_i)
            j_sub_p_I_intervals = model.NewOptionalIntervalVar(job_p_list[i][j][0], I + 30, job_p_list[i][j][2],
                                                               bool_sub_job_list[i][j], '%d_%d_I' % (i, j))
            x_t_intervals.append(j_sub_p_I_intervals)
            for m in range(len(total_C_list[i][j])):
                # de.append(int(17))
                y_end = model.NewIntVar(1, 6, 'y_end_%d%d%d' % (i, j, m))
                j_sub_p_intervals = model.NewOptionalIntervalVar(job_p_list[i][j][0], I + 1, job_p_list[i][j][1],
                                                                 total_C_list[i][j][m], 'p_%d_%d' % (i, j))
                j_sub_c_intervals = model.NewOptionalIntervalVar(m, 1, y_end, total_C_list[i][j][m], 'c_%d_%d' % (i, j))
                if job_type[i][j] == 1:
                    rs_c.append(j_sub_p_intervals)
                    de.append(int(17))
                x_intervals.append(j_sub_p_intervals)
                y_intervals.append(j_sub_c_intervals)

        model.AddNoOverlap(x_t_intervals)

    model.AddNoOverlap2D(x_intervals, y_intervals)
    model.AddCumulative(rs_c, de, 84)

    for i in range(len(job_data) - 1):
        model.Add(bool_job_list[i] == 1).OnlyEnforceIf(bool_job_list[i + 1])

    model.Maximize(total_bb)
    solver = cp_model.CpSolver()
    solver.parameters.num_search_workers = 8
    solver.parameters.max_time_in_seconds = t
    # solver.parameters.log_search_progress = True
    status = solver.Solve(model)

    if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
        print("status: ", status)
        for i in range(len(job_data)):
            solu_x_sub = []
            for j in range(len(job_data[i])):
                solution_x = []
                solution_x.append(solver.Value(job_p_list[i][j][0]))
                solution_x.append(solver.Value(job_p_list[i][j][1]))
                solu_x_sub.append(solution_x)
            solution_x_total.append(solu_x_sub)

        solution_x_total = solution_x_total + sxt

        for i in range(len(job_data)):
            solu_bool_sub = []
            for j in range(len(job_data[i])):
                solu_bool_sub.append(solver.Value(bool_sub_job_list[i][j]))
            solution_bool_sub_job.append(solu_bool_sub)

        solution_bool_sub_job = solution_bool_sub_job + sbsj

        for i in range(len(job_data)):
            solu_C_sub = []
            for j in range(len(job_data[i])):
                solu_C_sub_sub = []
                for k in range(len(total_C_list[i][j])):
                    solu_C_sub_sub.append(solver.Value(total_C_list[i][j][k]))
                solu_C_sub.append(solu_C_sub_sub)
            solution_bool_C.append(solu_C_sub)

        solution_bool_C = solution_bool_C + sbc

        print("youjie")
    else:
        print('No solution found.')

    return solution_x_total, solution_bool_sub_job, solution_bool_C


if __name__ == '__main__':
    t1 = time.time()
    data_end, data_end_se, job_type, d_list, d_list_se, data_start_se, job_se_type, car_type = generate_data(30, 4, 8)
    print("data_end: ", data_end)
    print("data_end_se: ", data_end_se)
    print("job_type: ", job_type)
    print("d_list: ", d_list)
    print("d_list_se: ", d_list_se)
    print("data_start_se: ", data_start_se)
    sxt, sbsj, sbc = se_solve(data_end_se, d_list_se, data_start_se, 60.0)
    print("sxt: ", sxt)
    print("sbsj: ", sbsj)
    print("sbc: ", sbc)
    solution_x_total, solution_bool_sub_job, solution_bool_C = solve(data_end, data_end_se, d_list, d_list_se,
                                                                     data_start_se, job_type, sxt, sbsj, sbc, 20.0)
    print("solution_x_total: ", solution_x_total)
    print("solution_bool_sub_job: ", solution_bool_sub_job)
    print("solution_bool_C: ", solution_bool_C)
    t2 = time.time()
    print("t: ", t2 - t1)

    car, job_re, re_job = load_fun_3(data_end + data_end_se, job_type + job_se_type, solution_bool_sub_job, car_type)
    print("car: ", car)
    print("job_re: ", job_re)
    print("re_job: ", re_job)
    print("hh")
